package com.progenies.ecg.asm31.model.codeparts;

import java.util.ArrayList;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import com.progenies.ecg.asm31.util.VariableRegister;
import com.progenies.ecg.generator.ClassGenerator;
import com.progenies.ecg.generator.ClassGeneratorFactory;
import com.progenies.ecg.model.AbstractCodeObject;
import com.progenies.ecg.model.codeparts.TryCatchCodePart;

public class TryCatchCodePartASM31 extends TryCatchCodePart implements IConfigurableCodePart
{
	protected MethodVisitor visitor;
	protected AbstractCodeObject parent;
	protected VariableRegister varRegister;

	public TryCatchCodePartASM31()
	{
		ClassGenerator clsGen=ClassGeneratorFactory.getClassGenerator();
		this.bodyBlock=clsGen.lineFactory.createCodeBlock();
		this.catchBlocks=new ArrayList<TryCatchCodePart.CatchBlock>();
		this.finallyBlock=clsGen.lineFactory.createCodeBlock();
	}
	
	@Override
	public void generateBytecode()
	{		
		//first create labels
		Label lblBody=new Label();
		Label lblEndBody=new Label();

		Label lblEnd=new Label();
		
		//Label for throwable catch block, used only if there is a finally clause
		Label lblStartThrowable=new Label();
		
		
		//for each catch clause, create label
		Label lblCatch[]=new Label[this.getCatchBlocks().size()];
		Label lblEndCatch[]=new Label[this.getCatchBlocks().size()];
		for(int i=0;i<lblCatch.length;i++)
		{
			lblCatch[i]=new Label();
			lblEndCatch[i]=new Label();
			
			//generate each try-catch-finally block
			visitor.visitTryCatchBlock(lblBody, lblEndBody, lblCatch[i], Type.getInternalName(this.getCatchBlocks().get(i).getExceptionClass()));
			
		}
		
		
		//if there is a finally block, i must construct another try-catch around every catch block to ensure it's execution
		if(this.finallyBlock!=null && finallyBlock.size()>0)
		{
			for(int i=0;i<lblCatch.length;i++)
				visitor.visitTryCatchBlock(i==0?lblBody:lblEndCatch[i-1], lblEndCatch[i], lblStartThrowable, null);
		}

		
		//Label of the body block and generate code
		visitor.visitLabel(lblBody);
		if(this.getBodyBlock()!=null)
			this.getBodyBlock().generateBytecode();

		visitor.visitLabel(lblEndBody);
		
		if(this.getFinallyBlock()!=null)
			this.getFinallyBlock().generateBytecode();
		
		
		visitor.visitJumpInsn(Opcodes.GOTO, lblEnd);
		
		//for each catch clause, create label and generate block
		for(int i=0;i<lblCatch.length;i++)
		{
			Type exceptionType=Type.getType(this.getCatchBlocks().get(i).getExceptionClass());
			
			visitor.visitLabel(lblCatch[i]);	
			
			//A exception is in stack, store into parameter
			int idx=varRegister.registerLocalVariable("ex"+i, exceptionType);
			visitor.visitLocalVariable("ex"+i, exceptionType.getDescriptor(), null, lblCatch[i], lblCatch[i], idx);
			visitor.visitVarInsn(exceptionType.getOpcode(Opcodes.ISTORE), idx);
			
			this.getCatchBlocks().get(i).generateBytecode();
			
			visitor.visitLabel(lblEndCatch[i]);
			
			//if there is a finally block, generate it at the end of the catch
			if(this.getFinallyBlock()!=null)
				this.getFinallyBlock().generateBytecode();
			
			visitor.visitJumpInsn(Opcodes.GOTO, lblEnd);
		}
		
		//throwable block that ensure that the finally block is executed
		if(this.finallyBlock!=null)
		{
			visitor.visitLabel(lblStartThrowable);
			
			//A exception is in stack, store into parameter
			Type exceptionType=Type.getType(Throwable.class);
			int idx=varRegister.registerLocalVariable("ex"+lblCatch.length, exceptionType);
			visitor.visitLocalVariable("ex"+lblCatch.length, exceptionType.getDescriptor(), null, lblStartThrowable, lblStartThrowable, idx);
			visitor.visitVarInsn(exceptionType.getOpcode(Opcodes.ISTORE), idx);
			
			//invoke finally block
			this.getFinallyBlock().generateBytecode();
			
			//load exception and throw
			visitor.visitVarInsn(exceptionType.getOpcode(Opcodes.ILOAD), idx);
			visitor.visitInsn(Opcodes.ATHROW);
		}
		
		
		//TODO: Common Finally block
//		visitor.visitLabel(lblFinally);
//		if(this.getFinallyBlock()!=null)
//			this.getFinallyBlock().generateBytecode();
		
		visitor.visitLabel(lblEnd);
		
		
		
		


	}

	
	
	
	@Override
	public boolean isReturnCodePart() {
		return false;
	}

	

	@Override
	public void configure(MethodVisitor visitor, AbstractCodeObject parent, VariableRegister varRegister)
	{
		this.visitor=visitor;
		this.parent=parent;
		this.varRegister=varRegister;
		
		if(this.getBodyBlock()!=null)
			((IConfigurableCodePart)this.getBodyBlock()).configure(visitor, parent, varRegister);
		
		if(this.getCatchBlocks()!=null)
		{
			for(CatchBlock block : this.getCatchBlocks())
			{
				((IConfigurableCodePart)block).configure(visitor, parent, varRegister);
			}
			
		}
		
		if(this.getFinallyBlock()!=null)
			((IConfigurableCodePart)this.getFinallyBlock()).configure(visitor, parent, varRegister);
	}
	
	
	
	
	
	
	public final static class CatchBlockASM31 extends CatchBlock implements IConfigurableCodePart
	{

		public CatchBlockASM31(Class<? extends Throwable> exceptionCls)
		{
			this.exceptionClass=exceptionCls;
			this.catchBody=ClassGeneratorFactory.getClassGenerator().lineFactory.createCodeBlock();
		}

		@Override
		public void generateBytecode() {
			this.getCatchBody().generateBytecode();
		}

		@Override
		public boolean isReturnCodePart() {
			return false;
		}

		@Override
		public void configure(MethodVisitor visitor, AbstractCodeObject constructorObjectASM31,	VariableRegister varRegister)
		{
			((IConfigurableCodePart)this.getCatchBody()).configure(visitor, constructorObjectASM31, varRegister);
		}
		
	}

}
